package com.elitel.frame.business.service.common.config;

import com.elitel.common.utils.ParamUtils;
import com.elitel.frame.business.service.impl.SqlUtlil;
import com.elitel.frame.main.entity.CfgExtsql;
import org.apache.ibatis.builder.xml.XMLMapperBuilder;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.session.Configuration;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Map;
import java.util.Set;

/**
 * 动态SQL加载器
 */
public class XMLMapperBuilderHelper {
    private Object addLock = new Object();
    private Object updateLock = new Object();


    /**
     * 加载动态SQL
     *
     * @param configuration
     * @param cfgExtsql
     */
    public void loadXmlConfig(Configuration configuration, CfgExtsql cfgExtsql) throws Exception {
        String serviceId = cfgExtsql.getGuid();
        String dtSql = cfgExtsql.getDtSql();
        //第一次初始化
        if (!ParamUtils.paramTypeMap.containsKey(serviceId)) {
            synchronized (addLock) {
                if (!ParamUtils.paramTypeMap.containsKey(serviceId)) {
                    add(configuration, cfgExtsql);
                    ParamUtils.paramTypeMap.put(serviceId, hashValue(dtSql));
                }
            }
        } else {
            // 判断是否需要重新加载配置
            if (!ParamUtils.paramTypeMap.get(serviceId).equals(hashValue(dtSql))) {
                synchronized (updateLock) {
                    if (!ParamUtils.paramTypeMap.get(serviceId).equals(hashValue(dtSql))) {
                        update(configuration, cfgExtsql);
                        ParamUtils.paramTypeMap.put(serviceId, hashValue(dtSql));
                    }
                }
            }
        }
    }

    /**
     * 添加动态SQL
     *
     * @param configuration
     * @param cfgExtsql
     */
    private void add(Configuration configuration, CfgExtsql cfgExtsql) throws Exception {
        String serviceId = cfgExtsql.getGuid();
        String dtSql = cfgExtsql.getDtSql();
        // 将动态SQL封装成XML格式
        String sqlXml = wrapSql2Xml(dtSql, serviceId);
        try (InputStream inputStream = new ByteArrayInputStream(sqlXml.getBytes(StandardCharsets.UTF_8))) {
            XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(inputStream,
                    configuration, serviceId,
                    configuration.getSqlFragments());
            xmlMapperBuilder.parse();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 更新动态SQL
     *
     * @param configuration
     * @param cfgExtsql
     */
    private void update(Configuration configuration, CfgExtsql cfgExtsql) throws Exception {
        String serviceId = cfgExtsql.getGuid();
        String dtSql = cfgExtsql.getDtSql();
        // 将动态SQL封装成XML格式
        String sqlXml = wrapSql2Xml(dtSql, serviceId);
        try (InputStream inputStream = new ByteArrayInputStream(sqlXml.getBytes(StandardCharsets.UTF_8))) {
            //1.删除已存在的旧资源
            Field loadedResourcesField = Configuration.class.getDeclaredField("loadedResources");
            Field mappedStatementsField = Configuration.class.getDeclaredField("mappedStatements");
            loadedResourcesField.setAccessible(true);
            mappedStatementsField.setAccessible(true);
            // 清理已加载的资源
            Set<String> loadedResources = (Set<String>) loadedResourcesField.get(configuration);
            loadedResources.removeIf(serviceId::equals);
            // 清理已映射的语句
            Map<String, MappedStatement> mappedStatements = (Map<String, MappedStatement>) mappedStatementsField.get(configuration);
            mappedStatements.remove(serviceId + ".dynamic");
            //2.添加新资源
            // 解析XML映射器
            XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(inputStream,
                    configuration, serviceId,
                    configuration.getSqlFragments());
            xmlMapperBuilder.parse();
        } catch (IOException e) {
            throw new RuntimeException(e);
        } catch (NoSuchFieldException e) {
            throw new RuntimeException(e);
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        }

    }

    private String wrapSql2Xml(String sql, String nameSpace) throws Exception {
        String s = SqlUtlil.dealLte(sql);

        if (sql.contains("<#if") && sql.contains("<#else")) {
            s = SqlUtlil.dealIfElseBlock(s);
        } else if (sql.contains("<#if")) {
            s = SqlUtlil.dealIfBlock(s);
        }
        String xml = "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n"
                + "<!DOCTYPE mapper PUBLIC \"-//mybatis.org//DTD Mapper 3.0//EN\" \"http://mybatis.org/dtd/mybatis-3-mapper.dtd\">"
                + "<mapper namespace=\"" + nameSpace + "\">"
                + "  <select id=\"dynamic\" resultType='java.util.Map'>"
                + s
                + "  </select>"
                + "</mapper>";
        return xml;
    }

    /**
     * hash 算法
     *
     * @param value
     * @return
     */
    private String hashValue(String value) {
        try {
            MessageDigest digest = MessageDigest.getInstance("MD5");
            byte[] hash = digest.digest(value.getBytes(StandardCharsets.UTF_8));
            StringBuilder hexString = new StringBuilder();
            for (byte b : hash) {
                String hex = Integer.toHexString(0xff & b);
                if (hex.length() == 1) hexString.append('0');
                hexString.append(hex);
            }
            return hexString.toString();
        } catch (NoSuchAlgorithmException e) {
            return null;
        }
    }

}